package com.shier.shiercodesandbox.core;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.shier.shiercodesandbox.core.CodeSandBox;
import com.shier.shiercodesandbox.model.*;
import com.shier.shiercodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 代码沙箱模板
 * 注意每个实现类必须自定义代码存放路径，参考{@link JavaNativeCodeSandbox}
 *
 * @author 程崎
 * @since 2023/09/01
 */
@Slf4j
public abstract class CodeSandboxTemplate implements CodeSandBox {

    String prefix;

    String globalCodeDirPath;

    String globalCodeFileName;

    /**
     * 超时时间，超过10秒则结束
     */
    public static final Long DEFAULT_TIME_OUT = 10000L;
    public static final long TIME_OUT = 10000L;

    /**
     * 每个实现类必须实现编译以及运行的cmd
     *
     * @param userCodeParentPath 代码所在的父目录
     * @param userCodePath       代码所在目录
     * @return {@link CodeSandboxCmd}
     */
    abstract CodeSandboxCmd getCmd(String userCodeParentPath, String userCodePath);

    /**
     * 保存代码到文件中，注意这里需要实现，不同编程语言要放到不同文件夹中
     * 保存到文件中的格式应为: UUID/代码文件，后面删除代码文件需要将代码文件的父文件删除
     *
     * @param code 代码
     * @return {@link File}
     */
    private File saveCodeToFile(String code) {
        String globalCodePath = System.getProperty("user.dir") + globalCodeDirPath;
        if (!FileUtil.exist(globalCodePath)) {
            FileUtil.mkdir(globalCodePath);
        }

        // 存放用户代码
        String userCodeParentPath = globalCodePath + prefix + File.separator + UUID.randomUUID();
        String userCodePath = userCodeParentPath + globalCodeFileName;
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    /**
     * 编译代码，注意编译代码要返回编译的信息
     *
     * @param compileCmd 编译命令
     * @return {@link ExecuteMessage}
     * @throws IOException IOException
     */
    private ExecuteMessage compileCode(String compileCmd) throws IOException {
        Process compileProcess = Runtime.getRuntime().exec(compileCmd);
        return ProcessUtils.handleProcessMessage(compileProcess, "编译");
    }


    /**
     * 4、获取响应输出结果
     *
     * @param executeMessageList
     * @return
     */
    public ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> executeMessageList) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        // 执行错误
        List<String> outputList = new ArrayList<>();
        // 取用时最大值，便于判断是否超时
        long maxTime = 0;
        for (ExecuteMessage executeMessage : executeMessageList) {
            String errorMessage = executeMessage.getErrorMessage();
            if (StrUtil.isNotBlank(errorMessage)) {
                executeCodeResponse.setMessage(errorMessage);
                // 用户提交程序执行中存在错误
                executeCodeResponse.setStatus(3);
                break;
            }
            outputList.add(executeMessage.getMessage());
            Long executeTime = executeMessage.getTime();
            if (executeTime != null) {
                maxTime = Math.max(maxTime, executeTime);
            }
        }
        // 正常执行完成
        if (outputList.size() == executeMessageList.size()) {
            executeCodeResponse.setStatus(1);
        }
        executeCodeResponse.setOutputList(outputList);
        JudgeInfo judgeInfo = new JudgeInfo();
        // todo 内存判断,需要借助第三方库获取内存占用，非常麻烦
        // judgeInfo.setMemory();
        judgeInfo.setTime(maxTime);
        executeCodeResponse.setJudgeInfo(judgeInfo);
        return executeCodeResponse;
    }


    /**
     * 运行代码
     *
     * @param inputList 输入用例
     * @param runCmd    运行的cmd
     * @return {@link List}<{@link ExecuteMessage}>
     * @throws RuntimeException RuntimeException
     */
    private List<ExecuteMessage> runCode(List<String> inputList, String runCmd) throws RuntimeException {
        List<ExecuteMessage> executeMessageList = new LinkedList<>();
        for (String input : inputList) {
            Process runProcess;
            Thread computeTimeThread;
            try {
                runProcess = Runtime.getRuntime().exec(runCmd);
                computeTimeThread = new Thread(() -> {
                    try {
                        Thread.sleep(DEFAULT_TIME_OUT);
                        if (runProcess.isAlive()) {
                            log.info("超时了，中断");
                            runProcess.destroy();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                });
                computeTimeThread.start();
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                ExecuteMessage executeMessage = ProcessUtils.handleProcessInteraction(runProcess, input, "运行");
                stopWatch.stop();
                computeTimeThread.stop();
                executeMessage.setTime(stopWatch.getLastTaskTimeMillis());
                executeMessageList.add(executeMessage);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
        return executeMessageList;
    }


    @Override
    public final ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        // 保存代码
        File userCodeFile = saveCodeToFile(code);
        // 生成c++路径
        String userCodePath = userCodeFile.getAbsolutePath();
        // c++输出路径
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
        // 执行命令
        CodeSandboxCmd cmdFromLanguage = getCmd(userCodeParentPath, userCodePath);
        // 组装c++执行命令
        String compileCmd = cmdFromLanguage.getCompileCmd();
        // 输出结果c++执行结果命令
        String runCmd = cmdFromLanguage.getRunCmd();
        // 编译代码
        try {
            ExecuteMessage executeMessage = compileCode(compileCmd);
            if (executeMessage.getExitCode() != 0) {
                FileUtil.del(userCodeParentPath);
                return ExecuteCodeResponse
                        .builder()
                        .status(2)
                        .message(executeMessage.getErrorMessage().substring(executeMessage.getErrorMessage().indexOf("main.cpp")))
                        .build();
            }
        } catch (Exception e) {
            FileUtil.del(userCodeParentPath);
            return errorResponse(e);
        }

        // 执行代码
        try {
            List<ExecuteMessage> executeMessageList = runCode(inputList, runCmd);
            ExecuteCodeResponse outputResponse = getOutputResponse(executeMessageList);
            return outputResponse;
        } catch (RuntimeException e) {
            FileUtil.del(userCodeParentPath);
            return errorResponse(e);
        }
    }

    final ExecuteCodeResponse errorResponse(Throwable e) {
        return ExecuteCodeResponse
                .builder()
                .outputList(new ArrayList<>())
                .message(e.getMessage())
                .judgeInfo(new JudgeInfo())
                .status(2)
                .build();
    }
}
